package com.anlu.base;

import com.anlu.base.database.DbManager;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
public class CacheStone {

    private static final int  CAPACITY = 3;

    private LruCache cache;

    private final DbManager dbManager;

    public CacheStone(DbManager dbManager) {
        this.dbManager = dbManager;
        initCapacity(CAPACITY);
    }

    /**
     * Init cache capacity.
     * @param capacity int
     */
    public void initCapacity(final int capacity) {
        if (cache == null) {
            cache = new LruCache(capacity);
        } else {
            cache.setCapacity(capacity);
        }
    }

    /**
     * Get user account using read-through cache.
     * @param userId {@link String}
     * @return {@link UserAccount}
     */
    public UserAccount readThrough(final String userId) {
        if(cache.contains(userId)){
            log.info("# Cache hit for userId: {}", userId);
            return cache.get(userId);
        }
        log.info("# Not found in cache! Go to DB!!");
        UserAccount userAccount = dbManager.readFromDb(userId);
        cache.set(userId, userAccount);
        return userAccount;
    }


    public void writeThrough(final UserAccount userAccount) {

        if(cache.contains(userAccount.getUserID())){
            dbManager.updateDb(userAccount);
        } else {
            dbManager.writeToDb(userAccount);
        }

        cache.set(userAccount.getUserID(), userAccount);
    }

    /**
     * Get user account using write-around cache.
     * @param userAccount {@link UserAccount}
     */
    public void writeAround(final UserAccount userAccount) {
        if (cache.contains(userAccount.getUserID())) {
            dbManager.updateDb(userAccount);
            // Cache data has been updated -- remove older
            cache.invalidate(userAccount.getUserID());
            // version from cache.
        } else {
            dbManager.writeToDb(userAccount);
        }
    }

    public UserAccount readThroughWithWriteBackPolicy(final String userId){
        if(cache.contains(userId)){
            log.info("# Found in cache!");
            return cache.get(userId);
        }
        log.info("# Not found in Cache!");

        UserAccount userAccount = dbManager.readFromDb(userId);
        if(cache.isFull()){
            log.info("# Cache is FULL! Writing LRU data to DB...");
            UserAccount toBeWrittenToDb = cache.getLruData();
            dbManager.upsertDb(toBeWrittenToDb);
        }
        cache.set(userId, userAccount);
        return userAccount;
    }

    public void writeBehind(final UserAccount userAccount) {

        if(cache.isFull() && !cache.contains(userAccount.getUserID())){
            log.info("# Cache is FULL! Writing LRU data to DB...");
            UserAccount toBeWrittenToDb = cache.getLruData();
            dbManager.upsertDb(toBeWrittenToDb);
        }

        cache.set(userAccount.getUserID(), userAccount);

    }

    public  void clearCache(){
        if (cache != null) {
            cache.clear();
        }
    }


    public void flushCache(){
        log.info("flush cache");
        Optional.ofNullable(cache)
                .map(LruCache::getCacheDataInListFrom)
                .orElse(List.of())
                .forEach(dbManager::updateDb);
        dbManager.disconnect();
    }

    /**
     * Print user accounts.
     * @return {@link String}
     */
    public String print(){
        return Optional.ofNullable(cache)
                .map(LruCache::getCacheDataInListFrom)
                .orElse(List.of())
                .stream()
                .map(userAccount -> userAccount.toString() + "\n")
                .collect(Collectors.joining("", "\n--CACHE CONTENT--\n", "----"));
    }


    public UserAccount get(final String userId){
        return cache.get(userId);
    }
    /**
     * Delegate to backing cache store.
     * @param userId {@link String}
     * @param userAccount {@link UserAccount}
     */
    public void set(final String userId, final UserAccount userAccount) {
        cache.set(userId, userAccount);
    }
    /**
     * Delegate to backing cache store.
     * @param userId {@link String}
     */
    public void invalidate(final String userId) {
        cache.invalidate(userId);
    }

}
